home *** CD-ROM | disk | FTP | other *** search
-
- PORTABLE THREAD SYNCHRONIZATION USING C++
-
- Jim Frost
- Software Tool & Die
- Copyright (c) 1995 Jim Frost
- All Rights Reserved
- Last changed February 17, 1995
-
- _________________________________________________________________
-
- Abstract
-
-
-
- This document provides example C++ classes implementing a series of
- synchronization objects useful for building portable multithreaded
- applications.
-
- This is a work in progress. Do not rely on the absolute accuracy of
- the implementations.
- _________________________________________________________________
-
- Table Of Contents
-
- * Introduction
- * The Mutex Class
- + Interface
- + Win32 implementation
- + Solaris 2.x implementation
- + Example of usage (protected class)
- * The Semaphore Class
- + Interface
- + Win32 implementation
- + Solaris 2.x implementation
- + Example of usage (queue template class)
- * The Event Class
- + Interface
- + Win32 implementation
- + Solaris 2.x implementation
- * The Gate Class
- + Interface
- + Win32 implementation
- + Solaris 2.x implementation
- * The Readers/Writer Class
- + Generic implementation
- + Solaris 2.x implementation
- * Fair-Share Readers/Writer Class
- + Generic implementation
-
-
- _________________________________________________________________
-
- Introduction
-
-
-
- Thread-aware operating systems are here, and are rapidly growing in
- popularity. Today there are at least three popular multithreaded
- operating systems -- Windows NT, OS/2, and Solaris 2.x -- and a POSIX
- standard, Pthreads, is on the way.
-
- While each of the operating systems provides very similar basic
- synchronization objects, they do so with considerably variant syntax
- and usage. As a result it is not trivial to write multithreaded
- applications which port between the operating systems easily.
-
- This document provides a series of C++ classes which implement both
- basic and more complex synchronization objects, as well as several
- useful data structures that use them. Implementations are provided for
- both Win32 (Windows NT and Windows 95) and Solaris 2.x environments.
- _________________________________________________________________
-
- The Mutex Class
-
-
-
- The simplest synchronization object is the mutex, which is used to
- allow only one thread at a time access to a resource (usually a data
- structure).
-
- The Mutex class implements a recursively lockable mutex, one where a
- single thread may lock the same mutex multiple times. In practice this
- is the safest and most useful form of a mutex.
-
- The interface to the Mutex class is:
-
-
- class Mutex {
- public:
- Mutex(void);
- void Lock(void);
- void Unlock(void);
- };
-
-
-
-
- See:
- * Win32 implementation
- * Solaris 2.x implementation
-
-
-
- Using the mutex class it is easy to create C++ classes which are
- totally thread-safe. For example:
-
-
- class SafeInteger : private Mutex {
- private:
- int value;
-
- public:
- void SetValue(int new_value) {
- Lock();
- value = new_value;
- Unlock();
- }
-
- int GetValue(void) {
- Lock();
- int ret_value = value;
- Unlock();
- return ret_value;
- }
- };
-
-
-
-
- Because the only access to the data member is through the member
- functions of its class, it is impossible to accidentally access the
- data unsafely.
- _________________________________________________________________
-
- The Semaphore Class
-
-
-
- The semaphore synchronization object is used so that one thread may
- allow one or more other threads to pass a particular point at a
- particular time.
-
- The interface to the Semaphore class is:
-
-
- class Semaphore {
- public:
- Semaphore(void);
- Semaphore(int available);
- ~Semaphore(void);
- void Wait(void);
- void Post(void);
- void Post(int how_many);
- };
-
-
-
-
- See:
- * Win32 implementation
- * Solaris 2.x implementation
-
-
-
- Semaphores are most useful when threads are in a producer/consumer
- relationship, as in the case of a queue. The following implements a
- queue template class using the Semaphore class:
-
-
- template <class T> class QueueEntry : public T {
- public:
- T value;
- QueueEntry<T>* next;
-
- QueueEntry(const T& item_value) {
- value = item_value;
- next = (QueueEntry*)0;
- }
- };
-
- template <class T> class Queue : private Semaphore : private Mutex {
- private:
- QueueEntry<T>* head;
- QueueEntry<T>* tail;
-
- public:
- Queue(void) {
- head = tail = (QueueEntry*)0;
- }
-
- void Add(const T& item_value) {
- Lock();
- if (tail == QueueEntry*)0)
- head = tail = new QueueEntry(item_value);
- else {
- tail->next = new QueueEntry(item_value);
- tail = tail->next;
- }
- Unlock();
- Post(); // wake up any waiting threads
- }
-
- T Wait(void) {
- Semaphore::Wait(); // wait for something to show up
- Lock();
- T value = head->value;
- QueueEntry* old = head;
- head = head->next;
- delete old;
- if (head == (QueueEntry*)0)
- tail = (QueueEntry*)0;
- Unlock();
- return value;
- }
- };
-
-
-
-
- Any number of threads may wait on a queue, and as data is added to the
- queue they will be woken up one-at-a-time. If no threads are waiting
- when data is added to the queue, any thread coming along at a later
- time will simply pick up with waiting data immediately.
- _________________________________________________________________
-
- The Event Class
-
-
-
- An event synchronization object is used to block one or more threads
- from proceeding until some specified time, at which time they are all
- released simultaneously.
-
- The interface to the Event class is:
-
-
- class Event {
- public:
- Event(void);
- ~Event(void);
- void Signal(void);
- void Wait(void);
- };
-
-
-
-
- See:
- * Win32 implementation
- * Solaris 2.x implementation
-
-
- _________________________________________________________________
-
- The Gate Class
-
-
-
- A gate is a synchronization object used to either stop all threads
- from proceeding through a point or to allow them all to proceed.
-
- The interface to the Gate class is:
-
-
- class Gate {
- public:
- Gate(void);
- ~Gate(void);
- void Open(void);
- void Close(void);
- void Release(void);
- void Wait(void);
- };
-
-
-
-
- See:
- * Win32 implementation
- * Solaris 2.x implementation
-
-
- _________________________________________________________________
-
- The Readers/Writer Lock Class
-
-
-
- In many multithreaded applications a data structure may be accessed by
- many threads, but modified only occasionally. Since it is safe for
- many threads to access a structure simultaneously so long as they
- don't change it, a readers/writer lock, which allows only one thread
- to modify a structure but any number of threads to inspect it when no
- modifications are taking place.
-
- Many operating systems supply readers/writer locks as primitives, but
- some do not. The following is a generic implementation of a
- readers/writer lock, with writer-priority, implemented completely on
- top of the classes described in this document.
-
-
- class RWLock : private Mutex {
- private:
- Semaphore write_lock; // used as a one-at-a-time release valve
- Gate read_barrier; // used to block/wakeup readers
- unsigned int writer_count; // # of writers waiting for or holding the lock
- unsigned int reader_count; // # of readers holding the lock
-
- public:
- ReadLock(void) {
- writer_count = 0;
- reader_count = 0;
- }
-
- void ReadLock(void) {
- Mutex::Lock();
-
- // wait until there are no more writers holding the lock
- while (writer_count > 0) {
- Mutex::Unlock();
- read_barrier.Wait();
- Mutex::Lock();
- }
- reader_count++;
- Mutex::Unlock();
- }
-
- void WriteLock(void) {
- Mutex::Lock();
- writer_count++; // this stops new readers from getting a lock
- write_lock.Wait(); // wait until the write lock is available
- read_barrier.Close(); // give new readers something to wait for
- Mutex::Unlock();
- }
-
- void Unlock(void) {
- Mutex::Lock();
- if (writer_count > 0) { // we must be a writer
- writer_count--;
- if (writer_count > 0) // another writer is waiting
- writer_lock.Post(); // let it go
- else
- read_barrier.Open(); // open the floodgates
- }
- else {
- reader_count--;
-
- // if we're the last reader and a writer is waiting, let it go
- if ((reader_count == 0) && (writer_count > 0)) {
- writer_lock.Post();
- }
- Mutex::Unlock();
- }
- };
-
-
- Note: The lack of an atomic release-semaphore-and-wait-for-event
- primitive on some systems requires the use of a Gate rather than an
- Event so that the event is not lost between the release of the
- structure lock by the reader(s) and the wait on the event.
-
- Note: Solaris 2.x supplies readers/writer locks as a primitive, making
- the implementation much simpler.
- _________________________________________________________________
-
- Fair-Share Readers/Writer Locks
-
-
-
- In some cases a single data structure will be accessed very often for
- both reading and writing. To avoid starvation of either the readers or
- the writers, a fair-share lock can be used. A generic implementation
- of the FairRWLock follows.
-
-
- class FairRWLock : private Mutex {
- private:
- Semaphore access_lock; // used as a one-at-a-time release valve
- Gate read_barrier; // used to block/wakeup readers
- unsigned int is_write_lock; // nonzero if locked for writing
- unsigned int writer_count; // # of writers waiting for or holding the loc
- k
- unsigned int reader_count; // # of readers holding the lock
- unsigned int readers_waiting; // # of readers waiting for the lock
-
- public:
- ReadLock(void) : access_lock(1) {
- is_write_lock = 0;
- writers_waiting = 0;
- reader_count = 0;
- readers_waiting = 0;
- }
-
- void ReadLock(void) {
- Mutex::Lock();
-
- // if there is at least one writer using the lock or waiting for it,
- // we need to wait for access
- if (writer_count > 0)) {
- if (readers_waiting++ == 0) // if we're the first reader in line
- Mutex::Unlock();
- access_lock.Wait(); // get the access lock
- Mutex::Lock();
- if (readers_waiting > 1) // then if there are other readers
- read_barrier.Open(); // let them go
- }
- else {
- Mutex::Unlock();
- read_barrier.Wait(); // otherwise wait until someone lets us go
- Mutex::Lock();
- }
- readers_waiting--;
- }
- reader_count++;
- Mutex::Unlock();
- }
-
- void WriteLock(void) {
- Mutex::Lock();
- writer_count++; // one more writer
- Mutex::Unlock();
- access_lock.Wait(); // wait until the access lock is available
- Mutex::Lock();
- is_write_lock = 1; // lock is a write lock
- read_barrier.Close(); // give readers something to wait for
- Mutex::Unlock();
- }
-
- void Unlock(void) {
- Mutex::Lock();
- if (is_write_lock) { // if this is a write lock
- is_write_lock = 0; // let it go
- writer_count--; // one less writer
- access_lock.Post(); // now let someone else have a chance
- }
- else if (--reader_count == 0) // if we're the last reader
- access_lock.Post(); // release the access lock
- Mutex::Unlock();
- }
- };
-
-
- Note: The fairness of this implementation depends on the fairness of
- the semaphore implementation.
- _________________________________________________________________
-